﻿/// <reference path="Core.ts" />

/**
 * Module for capturing and reconstituting Ext JS class definitions.
 */
module ExtSpec.ClassManager {
    'use strict';

    var configurations: any = {},
        callbacks: any = {};

    /**
     * Sets an Ext JS class definition.
     * @param {String} className The class name to set.
     * @param {Object|Function} configuration The Ext JS class definition as an object or function.
     * @param {Function} [callback] The callback function.
     */
    export function set(className: string, configuration: any, callback?: Function): void {
        if (!ExtSpec.isTypeOf(className, 'st')) {
            throw new Error('Class name argument must be a string.');
        }

        configurations[className] = configuration;

        if (ExtSpec.isTypeOf(callback, 'fn')) {
            callbacks[className] = callback;
        }
    }

    /**
     * Gets an Ext JS class definition.
     * @param {String} className The class name to get.
     * @returns {Object}
     */
    export function get(className: string): any {
        if (!configurations.hasOwnProperty(className)) {
            throw new Error('The class named "' + className + '" could not be found.');
        }

        return configurations[className];
    }

    /**
     * Builds a constructor for the given class.
     * @param {String} className The class name to construct.
     * @param {Function} [preConstruct] The function to apply to the instance before a custom constructor.
     * @returns {Function}
     */
    export function construct(className: string, preConstruct?: Function): any {
        var configuration: any = ClassManager.get(className),
            ctor = function () {
                if (ExtSpec.isTypeOf(preConstruct, 'fn')) {
                    preConstruct.call(this, ctor);
                }

                return this.constructor.apply(this, arguments);
            };

        if (ExtSpec.isTypeOf(configuration, 'fn')) {
            configuration = configuration(ctor);

            if (configuration.hasOwnProperty('override')) {
                throw new Error('Function form cannot be used for overrides (EXTJSIV-7805).');
            }
        }

        if (!ExtSpec.isObjectType(configuration)) {
            throw new Error('The definition for "' + className + '" must be an object type.');
        }

        if (!configuration.hasOwnProperty('constructor')) {
            configuration.constructor = function () {
                return this;
            };
        }

        if (configuration.statics) {
            ExtSpec.apply(ctor, configuration.statics);
        }

        if (configuration.inheritableStatics) {
            ExtSpec.apply(ctor, configuration.inheritableStatics);
        }

        ExtSpec.apply(ctor.prototype, configuration, ['statics', 'inheritableStatics']);
        ctor.prototype.self = ctor;

        return ctor;
    }

    /**
     * Creates and returns a new instance of the given class.
     * @param {String} className The class name to instantiate.
     * @param {Function} [preConstruct] The function to apply to the instance before a custom constructor.
     * @returns {Object}
     */
    export function create(className: string, preConstruct?: Function) {
        var Constructor = ClassManager.construct(className, preConstruct);

        return new Constructor();
    }

    /**
     * Gets the callback function passed to Ext.define.
     * @param {String} className The class name of the callback to get.
     * @returns {Function}
     */
    export function callback(className) {
        if (!callbacks.hasOwnProperty(className)) {
            throw new Error('The class named "' + className + '" was not defined with a callback.');
        }

        return callbacks[className];
    }
}

module ExtSpec {
    export var create = ExtSpec.ClassManager.create;
}

/**
 * Mock Ext module.
 */
module Ext {
    export declare var define: (className: string, configuration: any, callback?: Function) => any;

    var realDefine = Ext.define;

    /**
     * Sets an Ext JS class definition.
     * @param {String} className The class name to set.
     * @param {Object|Function} configuration The Ext JS class definition as an object or function.
     * @param {Function} [callback] The callback function.
     */
    function innerDefine(className: string, configuration: any, callback?: Function): any {
        ExtSpec.ClassManager.set.call(this, className, configuration, callback);

        if (realDefine) {
            return realDefine.call(this, className, configuration, callback);
        }
    }

    Object.defineProperty(Ext, 'define', {
        get: function () {
            return innerDefine;
        },
        set: function (value) {
            ExtSpec.warn('An attempt was made to replace Ext Spec\'s version of Ext.define. It is not recommended to use the real Ext JS library with Ext Spec. Both versions will now be executed.');
            realDefine = value;
        }
    });
}
